package com.fancy.common.utils.http;

import java.io.IOException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.HttpVersion;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

/**
 * HTTP连接管理器（实现了自动回复策略）
 * 
 * @author xinfeng.hu 2013-3-19下午1:22:21
 * @version 1.0.0
 * @category 杭州广桥网络技术有限公司(商业先知)
 */
public final class HttpConnManager {

	private static final int MAX_TOTAL_CONNS = 800;

	private static final int MAX_CONNS_PER_ROUTE = 20;

	private static final int CONN_TIMEOUT = 20 * 1000;

	private static final int MANAGER_TIMEOUT = 60 * 1000;

	private static final PoolingClientConnectionManager cm = new PoolingClientConnectionManager();

	private static final HttpClient httpClient = getDefaultHttpClient(cm);
	private static final HttpClient httpsClient = wrapClient(httpClient);

	// 模拟浏览器，解决一些服务器程序只允许浏览器访问的问题
	private static final HttpClient httpClientIE7 = getHttpClientByIE7(cm);

	// 模拟浏览器，解决一些服务器程序只允许浏览器访问的问题
	private static final HttpClient httpsClientIE7 = wrapClient(getHttpClientByIE7(cm));

	// 自定义的恢复策略
	private static final HttpRequestRetryHandler retryHandler = new DefaultHttpRequestRetryHandler();

	static {
		cm.setMaxTotal(MAX_TOTAL_CONNS);
		cm.setDefaultMaxPerRoute(MAX_CONNS_PER_ROUTE);
	}

	/**
	 * 包装成https
	 * 
	 * @param base
	 * @return
	 */
	private static HttpClient wrapClient(HttpClient base) {
		try {
			SSLContext ctx = SSLContext.getInstance("SSL");
			ctx.init(null, new TrustManager[] { new X509TrustManager() {
				@Override
				public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
				}

				@Override
				public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
				}

				@Override
				public X509Certificate[] getAcceptedIssuers() {
					return null;
				}
			} }, new java.security.SecureRandom());
			ClientConnectionManager manager = base.getConnectionManager();
			manager.getSchemeRegistry().register(new Scheme("https", 443, new SSLSocketFactory(ctx)));
			return new DefaultHttpClient(manager, base.getParams());
		} catch (Exception ex) {
			return null;
		}
	}

	/**
	 * 获取DefaultHttpClient实例
	 * 
	 * @param conman
	 *            连接管理
	 * @return DefaultHttpClient 对象
	 */
	private static DefaultHttpClient getDefaultHttpClient(final ClientConnectionManager cm) {
		DefaultHttpClient httpclient = new DefaultHttpClient(cm);
		httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, CONN_TIMEOUT);
		httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, CONN_TIMEOUT);
		httpclient.getParams().setParameter("http.connection-manager.timeout", MANAGER_TIMEOUT);
		httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Apache-HttpClient/4.2.1 (java 1.7; bizexpress api)");
		httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8");
		httpclient.setHttpRequestRetryHandler(HttpConnManager.retryHandler);
		// 异常自动恢复处理, 使用HttpRequestRetryHandler接口实现请求的异常恢复
		httpclient.setHttpRequestRetryHandler(retryHandler);
		return httpclient;
	}

	/**
	 * 获取模拟浏览器的HttpClient实例
	 * 
	 * @param conman
	 *            连接管理
	 * @return DefaultHttpClient 对象
	 */
	private static DefaultHttpClient getHttpClientByIE7(final ClientConnectionManager cm) {
		DefaultHttpClient httpclient = new DefaultHttpClient(cm);
		httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
		// 模拟浏览器，解决一些服务器程序只允许浏览器访问的问题
		httpclient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)");
		httpclient.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, Boolean.FALSE);
		httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, "UTF-8");
		// 异常自动恢复处理, 使用HttpRequestRetryHandler接口实现请求的异常恢复
		httpclient.setHttpRequestRetryHandler(HttpConnManager.retryHandler);
		return httpclient;
	}

	/**
	 * 获得一个受连接管理器管理的http客户端
	 * 
	 * @return
	 */
	public static HttpClient getHttpClient() {
		return getHttpClient(false);
	}

	/**
	 * 获得一个受连接管理器管理的https客户端
	 * 
	 * @return
	 */
	public static HttpClient getHttpsClient() {
		return getHttpsClient(false);
	}

	/**
	 * 获得一个受连接管理器管理的http客户端
	 * 
	 * @param byBrowser
	 *            是否模拟浏览器
	 * @return
	 */
	public static HttpClient getHttpClient(boolean byBrowser) {
		return (byBrowser ? httpClientIE7 : httpClient);
	}

	/**
	 * 获得一个受连接管理器管理的https客户端
	 * 
	 * @param byBrowser
	 *            是否模拟浏览器
	 * @return
	 */
	public static HttpClient getHttpsClient(boolean byBrowser) {
		return (byBrowser ? httpsClientIE7 : httpsClient);
	}

	/**
	 * 获取连接管理器
	 * 
	 * @return
	 */
	public static PoolingClientConnectionManager getCm() {
		return cm;
	}

	/**
	 * 异常回复解决类
	 * 
	 * @author xinfeng.hu 2013-3-28下午5:54:17
	 * @version 1.0.0
	 * @category 杭州广桥网络技术有限公司(商业先知)
	 */
	static class DefaultHttpRequestRetryHandler implements HttpRequestRetryHandler {

		/**
		 * 异常恢复
		 */
		public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
			// 设置恢复策略，在发生异常时候将自动重试3次
			if (executionCount >= 3) {
				return false;
			}
			// 服务器断开连接，重试
			if (exception instanceof NoHttpResponseException) {
				return true;
			}
			// SSL握手异常，放弃重试
			if (exception instanceof SSLHandshakeException) {
				return false;
			}
			HttpRequest request = (HttpRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);
			boolean idempotent = (request instanceof HttpEntityEnclosingRequest);

			// Retry if the request is considered idempotent
			if (!idempotent) {
				return true;
			}
			return false;
		}
	}
}
